package main

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"

	wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"

	// "runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
)

// App struct
type App struct {
	ctx         context.Context
	taskCounter uint64 // 添加任务计数器
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &App{
		taskCounter: 0,
	}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
}

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
	return fmt.Sprintf("Hello %s, It's show time!", name)
}

// SelectDirectory opens a dialog to select a directory
func (a *App) SelectDirectory() (string, error) {
	dir, err := wailsRuntime.OpenDirectoryDialog(a.ctx, wailsRuntime.OpenDialogOptions{
		Title: "选择目录",
	})
	// 使用 Wails 的 dialog 包来打开选择目录的对话框
	if err != nil {
		return "", err
	}
	return dir, nil
}

// SelectFile opens a dialog to select a file
func (a *App) SelectFile() (string, error) {
	// 使用 Wails 的 dialog 包来打开选择文件的对话框
	file, err := wailsRuntime.OpenFileDialog(a.ctx, wailsRuntime.OpenDialogOptions{
		Title: "选择文件",
	})
	if err != nil {
		return "", err
	}
	return file, nil
}

// ConvertSingleFile 处理单个文件的转换
func (a *App) ConvertSingleFile(inputFile string, outputDir string, useOutputExt bool, outputExt string, outputOptions string) (string, error) {
	// 验证输入参数
	if inputFile == "" || outputDir == "" {
		return "", fmt.Errorf("输入文件或输出目录不能为空")
	}

	// 构建输出文件路径
	inputFileName := filepath.Base(inputFile)
	fileNameWithoutExt := strings.TrimSuffix(inputFileName, filepath.Ext(inputFileName))

	var outputPath string
	if useOutputExt && outputExt != "" {
		if !strings.HasPrefix(outputExt, ".") {
			outputExt = "." + outputExt
		}
		outputPath = filepath.Join(outputDir, fileNameWithoutExt+outputExt)
	} else {
		// 如果没有指定输出扩展名，使用输入文件的扩展名
		outputPath = filepath.Join(outputDir, inputFileName)
	}

	// 检查输出文件是否已存在
	if _, err := os.Stat(outputPath); err == nil {
		return "", fmt.Errorf("输出文件已存在: %s", outputPath)
	}

	// 构建ffmpeg命令
	ffmpegPath := filepath.Join(".", "ffmpeg", "bin", "ffmpeg.exe")
	args := []string{
		"-y",           // 覆盖已存在的文件
		"-hide_banner", // 隐藏ffmpeg的banner信息
		// "-stats",        // 显示转换进度信息
		"-i", inputFile, // 输入文件
		"-progress", "pipe:2", // 实时输出进度信息
	}

	// 添加输出选项
	if outputOptions != "" {
		// 将输出选项字符串分割成参数数组
		optArgs := strings.Split(outputOptions, " ")
		args = append(args, optArgs...)
	}

	// 添加输出文件路径
	args = append(args, outputPath)

	// 获取视频时长
	duration, err := getVideoDuration(inputFile)
	if err != nil {
		return "", fmt.Errorf("无法获取视频时长: %v", err)
	}

	// 创建命令
	cmd := exec.Command(ffmpegPath, args...)

	// 设置进程属性，隐藏窗口
	cmd.SysProcAttr = &syscall.SysProcAttr{
		HideWindow: true,
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return "", fmt.Errorf("无法创建错误输出管道: %v", err)
	}

	// 启动命令
	if err := cmd.Start(); err != nil {
		return "", fmt.Errorf("启动命令失败: %v", err)
	}

	// 创建一个缓冲区来存储输出
	var outputBuilder strings.Builder

	// 创建一个WaitGroup来等待所有goroutine完成
	var wg sync.WaitGroup
	wg.Add(1)

	// 处理错误输出
	go func() {
		defer wg.Done()
		scanner := bufio.NewScanner(stderr)

		// 为每个任务生成唯一ID，使用原子操作递增计数器
		currentTaskNum := atomic.AddUint64(&a.taskCounter, 1)
		taskId := fmt.Sprintf("task_%d_%s", currentTaskNum, inputFileName)

		for scanner.Scan() {
			line := scanner.Text()
			outputBuilder.WriteString(line + "\n")
			var lastProgress int = -1 // 初始值 -1，确保0也会被发出

			if strings.HasPrefix(line, "out_time=") {
				timeStr := strings.TrimPrefix(line, "out_time=")
				current := parseTime(timeStr)
				if duration > 0 {
					progress := int((current / duration) * 100)
					if progress > lastProgress {
						lastProgress = progress
						// wailsRuntime.EventsEmit(a.ctx, "ffmpeg_progress", taskId, progress)
						wailsRuntime.EventsEmit(a.ctx, "ffmpeg_progress", map[string]interface{}{
							"taskId":   taskId,
							"progress": progress,
						})
					}
				}
			}

			// 还可以监听 progress=end 来确定是否完成
			if strings.HasPrefix(line, "progress=") {
				if strings.TrimSpace(line) == "progress=end" {
					// wailsRuntime.EventsEmit(a.ctx, "ffmpeg_done", taskId, outputPath)
					wailsRuntime.EventsEmit(a.ctx, "ffmpeg_done", map[string]interface{}{
						"taskId":     taskId,
						"outputPath": outputPath,
					})
				}
			}

			// 日志也发到前端
			// wailsRuntime.EventsEmit(a.ctx, "ffmpeg_log", line)
		}
	}()

	// 等待命令完成
	err = cmd.Wait()
	wg.Wait()

	if err != nil {
		return outputBuilder.String(), fmt.Errorf("ffmpeg执行失败: %v", err)
	}

	// 检查输出文件是否存在并且大小大于0
	if fileInfo, err := os.Stat(outputPath); err != nil || fileInfo.Size() == 0 {
		return outputBuilder.String(), fmt.Errorf("转换失败：输出文件不存在或大小为0")
	}

	return outputBuilder.String(), nil
}

// ConvertBatchFiles 批量处理目录中的文件
func (a *App) ConvertBatchFiles(inputDir string, outputDir string, useOutputExt bool, outputExt string, outputOptions string, fileType string) (string, error) {
	// 验证输入参数
	if inputDir == "" || outputDir == "" {
		return "", fmt.Errorf("输入目录或输出目录不能为空")
	}

	// 根据文件类型设置扩展名过滤器
	var extensions []string
	switch fileType {
	case "video":
		extensions = []string{".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v", ".mpg", ".mpeg", ".vob", ".ogv", ".mxf", ".ts", ".m2ts", ".divx", ".3gp", ".asf", ".rm", ".rmvb"}
	case "audio":
		extensions = []string{".mp3", ".wav", ".aac", ".flac", ".m4a", ".wma", ".ogg", ".ac3", ".aiff", ".alac", ".ape", ".opus", ".ra", ".wv", ".mka", ".dts", ".mpc"}
	case "image":
		extensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".tga", ".dpx", ".exr", ".pam", ".pbm", ".pcx", ".pgm", ".ppm", ".sgi", ".xwd", ".heic"}
	default:
		return "", fmt.Errorf("不支持的文件类型: %s", fileType)
	}

	// 创建一个通道用于收集转换结果
	type conversionResult struct {
		filename string
		output   string
		err      error
	}
	results := make(chan conversionResult)

	// 遍历目录并开始转换
	go func() {
		var wg sync.WaitGroup
		// 限制最大并发数为CPU核心数
		// 将并发数限制在CPU核心数的75%，最小为2，最大为8
		// maxWorkers := runtime.NumCPU() * 3 / 4
		// if maxWorkers < 1 {
		//     maxWorkers = 1
		// } else if maxWorkers > 8 {
		//     maxWorkers = 8
		// }
		maxWorkers := 2
		semaphore := make(chan struct{}, maxWorkers)

		err := filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			// 跳过目录
			if info.IsDir() {
				return nil
			}

			// 检查文件扩展名
			ext := strings.ToLower(filepath.Ext(path))
			isValidExt := false
			for _, validExt := range extensions {
				if ext == validExt {
					isValidExt = true
					break
				}
			}

			if !isValidExt {
				return nil
			}

			wg.Add(1)
			go func(filePath string) {
				defer wg.Done()

				// 获取信号量
				semaphore <- struct{}{}
				defer func() { <-semaphore }()

				// 为每个任务生成唯一ID
				// taskId := fmt.Sprintf("task_%s", filepath.Base(filePath))

				// 构建输出文件名
				fileName := filepath.Base(filePath)
				// fileNameWithoutExt := strings.TrimSuffix(fileName, filepath.Ext(fileName))

				// 调用单文件转换函数
				output, err := a.ConvertSingleFile(
					filePath,
					outputDir,
					useOutputExt,
					outputExt,
					outputOptions,
				)

				results <- conversionResult{
					filename: fileName,
					output:   output,
					err:      err,
				}
			}(path)

			return nil
		})

		if err != nil {
			results <- conversionResult{
				filename: "",
				output:   "",
				err:      fmt.Errorf("遍历目录失败: %v", err),
			}
		}

		// 等待所有转换完成
		wg.Wait()
		close(results)
	}()

	// 收集所有转换结果
	var outputBuilder strings.Builder
	for result := range results {
		if result.err != nil {
			outputBuilder.WriteString(fmt.Sprintf("转换文件 %s 失败: %v\n", result.filename, result.err))
		} else {
			outputBuilder.WriteString(fmt.Sprintf("转换文件 %s 成功:\n%s\n", result.filename, result.output))
		}
	}
	wailsRuntime.EventsEmit(a.ctx, "ffmpeg_done", map[string]interface{}{
		"allTask": 1,
		"output":  outputBuilder.String(),
	})

	return outputBuilder.String(), nil
}

// parseTime 将时间字符串转换为秒数
func parseTime(timeStr string) float64 {
	parts := strings.Split(timeStr, ":")
	if len(parts) != 3 {
		return 0
	}

	hours, _ := strconv.ParseFloat(parts[0], 64)
	minutes, _ := strconv.ParseFloat(parts[1], 64)
	seconds, _ := strconv.ParseFloat(parts[2], 64)

	return hours*3600 + minutes*60 + seconds
}

func getVideoDuration(filePath string) (float64, error) {
	ffprobePath := filepath.Join(".", "ffmpeg", "bin", "ffprobe.exe")

	// 使用 -v error 和 -show_entries 来获取 duration
	cmd := exec.Command(ffprobePath,
		"-v", "error",
		"-show_entries", "format=duration",
		"-of", "default=noprint_wrappers=1:nokey=1",
		filePath,
	)

	// 获取输出
	output, err := cmd.Output()
	if err != nil {
		return 0, fmt.Errorf("获取视频时长失败: %v", err)
	}

	// 解析输出为 float64（秒）
	durationStr := strings.TrimSpace(string(output))
	duration, err := strconv.ParseFloat(durationStr, 64)
	if err != nil {
		return 0, fmt.Errorf("解析时长失败: %v", err)
	}

	return duration, nil
}
